package com.javacoo.event.client.support.ast;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

import com.google.auto.service.AutoService;
import com.javacoo.event.client.annotation.EventHandler;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;

import lombok.extern.slf4j.Slf4j;

/**
 * EventHandler注解处理器
 * <li></li>
 *
 * @author: duanyong@jccfc.com
 * @since: 2021/10/19 10:42
 */
@Slf4j
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.javacoo.event.client.annotation.EventHandler")
public class EventHandlerProcessor extends AbstractProcessor {
    /**
     * spring注解包
     */
    private static final String SPRING_ANNONATION_PACKAGE = "org.springframework.stereotype";
    /**
     * 打印log
     */
    private Messager messager;
    /**
     * 抽象语法树
     */
    private JavacTrees trees;
    /**
     * 封装了创建AST节点的一些方法
     */
    private TreeMaker treeMaker;
    /**
     * 提供了创建标识符的一些方法
     */
    private Names names;
    private JavacElements elementUtils;
    // 初始化方法
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
        this.elementUtils = (JavacElements) processingEnv.getElementUtils();
    }

    // 真正处理注解的方法
    @Override
    public synchronized boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        printLog("获取所有注解:{}",annotations.toString());
        //包含注解的类集合
        Set<Element> classElements = new HashSet<>();
        // 获取所有包含EventHandler注解的Element
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(EventHandler.class);
        //处理所有包含EventHandler注解的Element,添加注解并导入包
        set.forEach(element -> {
            JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) elementUtils.getTree(element);
            //获取注解
            EventHandler eventHandler = element.getAnnotation(EventHandler.class);
            //如果是线程安全的 则添加AllowConcurrentEvents注解
            if(eventHandler != null && eventHandler.threadSafe()){
                //添加AllowConcurrentEvents注解
                addMethodAnnotation(jcMethodDecl,"com.google.common.eventbus.AllowConcurrentEvents");
                //导入包
                addConsumerImport(element,"com.google.common.eventbus","AllowConcurrentEvents");
            }
            //添加Subscribe注解
            addMethodAnnotation(jcMethodDecl,"com.google.common.eventbus.Subscribe");
            //导入包
            addConsumerImport(element,"com.google.common.eventbus", "Subscribe");
            //记录注解所在类
            classElements.add(element.getEnclosingElement());
            printLog("包含注解所在类:{}",element.getEnclosingElement().getSimpleName());
        });
        //处理注解所在类添加初始化方法
        classElements.forEach(element -> {
            addClassAnnotation(element,"org.springframework.stereotype.Component");
            //导入包
            addConsumerImport(element,"com.javacoo.event.client.starter","EventHolder");
            //构建方法
            JCTree.JCMethodDecl jcMethodDecl = makeEventListeningRegisterMethodDecl();
            //添加注解
            addMethodAnnotation(jcMethodDecl,"javax.annotation.PostConstruct");
            //添加类方法
            addClassMethod(element,jcMethodDecl);
        });
        return true;
    }
    /**
     * 添加类方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:36
     * @param element: 类element对象
     * @param jcMethodDecl: 方法
     * @return: void
     */
    private void addClassMethod(Element element,JCTree.JCMethodDecl jcMethodDecl) {
        printLog("添加类方法:{}",jcMethodDecl.name);
        JCTree jcTree = trees.getTree(element);
        //获取源注解的参数
        jcTree.accept(new TreeTranslator(){
            @Override
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                jcClassDecl.defs=jcClassDecl.defs.append(jcMethodDecl);
                super.visitClassDef(jcClassDecl);
            }
        });
    }
    /**
     * 添加方法注解
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:38
     * @param jcMethodDecl: 方法
     * @param annotaionName: 注解名称
     * @return: void
     */
    private void addMethodAnnotation(JCTree.JCMethodDecl jcMethodDecl,String annotaionName){
        printLog("添加方法注解:{}",annotaionName);
        JCTree.JCAnnotation jcAnnotation = makeAnnotation(annotaionName, List.nil());
        jcMethodDecl.mods.annotations = jcMethodDecl.mods.annotations.append(jcAnnotation);
    }
    /**
     * 添加类注解
     * <p>说明:</p>
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/27 22:05
     */
    private void addClassAnnotation(Element element,String annotaionName) {
        JCTree jcTree = trees.getTree(element);
        //获取源注解的参数
        jcTree.accept(new TreeTranslator(){
            @Override
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                //是否包含spring注解
                boolean incAnno = jcClassDecl.mods.annotations.stream().anyMatch(annotation ->annotation.type.toString().contains(SPRING_ANNONATION_PACKAGE));
                //boolean incAnno = jcClassDecl.mods.annotations.stream().anyMatch(annotation ->annotation.type.toString().equals(jcAnnotation.annotationType.toString()));
                printLog("是否包含待添加注解:{}",incAnno);
                if(incAnno){
                   return;
                }
                JCTree.JCAnnotation jcAnnotation = makeAnnotation(annotaionName, List.nil());
                printLog("类添加注解:{}",jcAnnotation.toString());
                jcClassDecl.mods.annotations = jcClassDecl.mods.annotations.append(jcAnnotation);
                jcClassDecl.mods.annotations.forEach(e -> {
                    printLog("当前类包含注解:{}",e.toString());
                });
                super.visitClassDef(jcClassDecl);
            }
        });
    }

    /**
     * 日志打印
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:38
     * @param ss: 信息
     * @param args: 参数
     * @return: void
     */
    private void printLog(String ss,Object... args){
        if(args.length>0) {
            ss = ss.replace("{}", "%s");
            String logs = String.format(ss, args);
            messager.printMessage(Diagnostic.Kind.NOTE, logs);
        }else{
            messager.printMessage(Diagnostic.Kind.NOTE, ss);
        }
    }
    /**
     * 构建事件监听对象注册方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 9:19
     * @return: com.sun.tools.javac.tree.JCTree.JCMethodDecl
     */
    private JCTree.JCMethodDecl makeEventListeningRegisterMethodDecl() {
        printLog("构建事件监听对象注册方法");
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        statements.append(treeMaker.Exec(
                    treeMaker.Apply(
                        List.<JCTree.JCExpression>nil(),
                        treeMaker.Select(
                            treeMaker.Ident(
                                elementUtils.getName("EventHolder")
                            ),
                            elementUtils.getName("register")
                        ),
                        List.<JCTree.JCExpression>of(
                            treeMaker.Ident(names.fromString("this"))
                        )
                    )
                ));
        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), names.fromString("eventListeningRegister"), treeMaker.Type(new Type.JCVoidType()), List.nil(), List.nil(), List.nil(), body, null);
    }
    /**
     * 构建注解对象
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:39
     * @param annotaionName:注解名称
     * @param args: 注解参数
     * @return: com.sun.tools.javac.tree.JCTree.JCAnnotation
     */
    private JCTree.JCAnnotation makeAnnotation(String annotaionName, List<JCTree.JCExpression> args) {
        JCTree.JCExpression expression = chainDots(annotaionName.split("\\."));
        JCTree.JCAnnotation jcAnnotation=treeMaker.Annotation(expression, args);
        return jcAnnotation;
    }
    /**
     * 构建依赖包导入
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:40
     * @param packageName:
     * @param className:
     * @return: com.sun.tools.javac.tree.JCTree.JCImport
     */
    private JCTree.JCImport buildImport(String packageName, String className) {
        JCTree.JCIdent ident = treeMaker.Ident(names.fromString(packageName));
        JCTree.JCImport jcImport = treeMaker.Import(treeMaker.Select(ident, names.fromString(className)), false);
        printLog("构建依赖包导入:{}",jcImport.toString());
        return jcImport;
    }
    /**
     * 导入依赖包
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/10/21 11:40
     * @param element: 类element对象
     * @return: void
     */
    private void addConsumerImport(Element element,String packageName, String className) {
        TreePath treePath = trees.getPath(element);
        JCTree.JCCompilationUnit jccu = (JCTree.JCCompilationUnit) treePath.getCompilationUnit();
        java.util.List<JCTree> trees = new ArrayList<>();
        trees.addAll(jccu.defs);
        JCTree.JCImport custImport = buildImport(packageName, className);
        if (!trees.contains(custImport)) {
            trees.add(0,custImport);
        }
        jccu.defs=List.from(trees);
    }
    private JCTree.JCExpression chainDots(String... elems) {
        assert elems != null;
        JCTree.JCExpression e = null;
        for (int i = 0 ; i < elems.length ; i++) {
            e = e == null ? treeMaker.Ident(names.fromString(elems[i])) : treeMaker.Select(e, names.fromString(elems[i]));
        }
        assert e != null;
        return e;
    }
}
